package com.nulldev.util.web.YouTubeExtractor.related;

import static com.nulldev.util.web.YouTubeExtractor.YouTubeExtractorConstants.PAGE_CL;
import static com.nulldev.util.web.YouTubeExtractor.YouTubeExtractorConstants.PAGE_LABEL;
import static com.nulldev.util.web.YouTubeExtractor.YouTubeExtractorConstants.UTC_OFFSET;
import static com.nulldev.util.web.YouTubeExtractor.YouTubeExtractorConstants.VARIANTS_CHECKSUM;

import java.time.Duration;

import com.nulldev.util.data.Variables;
import com.nulldev.util.data.Arrays.maps.FastMaps;
import com.nulldev.util.languages.json.uniapi.UniJsonValue;
import com.nulldev.util.logging.Logger;
import com.nulldev.util.logging.LoggerFactory;
import com.nulldev.util.web.HttpClient.HttpClient;
import com.nulldev.util.web.HttpClient.HttpMethod;
import com.nulldev.util.web.HttpClient.HttpResponse;
import com.nulldev.util.web.URLUtils.Entry;
import com.nulldev.util.web.YouTubeExtractor.YouTubeExtractorConstants;
import com.nulldev.util.web.YouTubeExtractor.extractors.util.YTIAPIClientType;
import com.nulldev.util.web.YouTubeExtractor.extractors.util.YTIAPIClientType.YTIAPIClientEnums;
import com.nulldev.util.web.YouTubeExtractor.util.YTRequestObject;

public class RelatedContinuations {

	private static final Logger log = LoggerFactory.getLogger();

	public static UniJsonValue getRelatedObject(final String videoID, final YTIAPIClientType clientType, final String continuation) throws Exception {
		Variables.requireNonNull(continuation, "continuation");
		Variables.requireNonNull(videoID, "videoID");
		Variables.requireNonNull(clientType, "clientType");
		final UniJsonValue o = YTRequestObject.makeYTRequestObject(FastMaps.fixedUniMap(new Object[][]
			{
					{ YTRequestObject.YT_REQUEST_VIDEO_ID, videoID },
					{ YTRequestObject.YT_ENABLE_PLAYBACK_CONTEXT, true },
					{ YTRequestObject.YT_REQUEST_CLIENT_TYPE, clientType } }));
		o.add("continuation", continuation);
		/* */
		final String API_KEY = clientType.contains(YTIAPIClientEnums.FallbackAPIKey) ? clientType.get(YTIAPIClientEnums.FallbackAPIKey)
				: YouTubeExtractorConstants.API_KEY;
		final String forcedUserAgent = clientType.contains(YTIAPIClientEnums.CustomUserAgent) ? clientType.get(YTIAPIClientEnums.CustomUserAgent) : null;
		final String CLIENT_VERSION = clientType.contains(YTIAPIClientEnums.FallbackVersion) ? clientType.get(YTIAPIClientEnums.FallbackVersion)
				: YouTubeExtractorConstants.CLIENT_VERSION;
		final HttpResponse<String> response = HttpClient.getString().url("https://www.youtube.com/youtubei/v1/next?key=" + API_KEY + "&prettyPrint=false")
				.timeout(Duration.ofSeconds(35)).forceHeader("User-Agent", forcedUserAgent).header("Content-Type", "application/json").headers(new Entry[]
				{ new Entry("X-YouTube-Client-Name", clientType.get(YTIAPIClientEnums.ContextClientName)),
						new Entry("X-YouTube-Client-Version", CLIENT_VERSION), new Entry("X-YouTube-Page-CL", PAGE_CL),
						new Entry("X-YouTube-Page-Label",
								clientType.contains(YTIAPIClientEnums.IsMobileContext) ? PAGE_LABEL.replace(".desktop_", ".mobile_") : PAGE_LABEL),
						new Entry("X-YouTube-Utc-Offset", UTC_OFFSET), new Entry("X-YouTube-Variants-Checksum", VARIANTS_CHECKSUM),
						new Entry("Referer", "https://www.youtube.com/watch?v=" + videoID),
						new Entry("X-SPF-Previous", "https://www.youtube.com/watch?v=" + videoID),
						new Entry("X-SPF-Referer", "https://www.youtube.com/watch?v=" + videoID), })
				.create(HttpMethod.POST, o.toString());
		final String data = response.dataThenClose();
		if (data.isEmpty()) {
			throw new UnsupportedOperationException("Failed to get related, RESPONSE_WAS_EMPTY!");
		} else if (data.equalsIgnoreCase("{\"reload\":\"now\"}")) {
			throw new UnsupportedOperationException("Failed to get related, RESPONSE_WANTED_REFRESH!");
		} else if (data.startsWith("<!DOCTYPE")) {
			throw new UnsupportedOperationException("Failed to get related, RESPONSE_WAS_HTML!");
		} else {
			final UniJsonValue obj = UniJsonValue.parse(data);
			if (!obj.contains("onResponseReceivedEndpoints")) {
				log.error("Failed to find onResponseReceivedEndpoints!");
				return null;
			}
			final UniJsonValue onResponseReceivedEndpoints = obj.get("onResponseReceivedEndpoints").asArray();
			if (onResponseReceivedEndpoints.length() != 1) {
				log.error("Unexpected onResponseReceivedEndpoints size! (got " + onResponseReceivedEndpoints.size() + " wanted 1)");
				return null;
			}
			final UniJsonValue firstItem = onResponseReceivedEndpoints.first();
			if (!firstItem.contains("appendContinuationItemsAction")) {
				log.error("Failed to find appendContinuationItemsAction!");
				return null;
			}
			final UniJsonValue appendContinuationItemsAction = firstItem.get("appendContinuationItemsAction");
			if (!appendContinuationItemsAction.contains("continuationItems")) {
				log.error("Failed to find appendContinuationItemsAction!");
				return null;
			}
			// XXX: In theory we could check if
			// appendContinuationItemsAction[targetId]="watch-next-feed"
			final UniJsonValue continuationItems = appendContinuationItemsAction.get("continuationItems").asArray();
			return continuationItems;
		}
	}
}
